﻿//------------------------------------------------------------------------------
// <copyright file="LocateInSolution.cs" company="Company">
//     Copyright (c) Company.  All rights reserved.
// </copyright>
//------------------------------------------------------------------------------

using System;
using System.ComponentModel.Design;
using System.Globalization;
using Microsoft.VisualStudio.Shell;
using Microsoft.VisualStudio.Shell.Interop;
using EnvDTE;
using System.Collections;
using EnvDTE80;

namespace OnePieceAssist
{
    /// <summary>
    /// Command handler
    /// </summary>
    internal sealed class LocateInSolution
    {
        /// <summary>
        /// Command ID.
        /// </summary>
        public const int CommandId = 256;

        /// <summary>
        /// Command menu group (command set GUID).
        /// </summary>
        public static readonly Guid CommandSet = new Guid("02ad63b0-4324-4cb6-828c-d9596c4fc8ec");

        /// <summary>
        /// VS Package that provides this command, not null.
        /// </summary>
        private readonly Package package;

        private DTE2 _applicationObject;
        /// <summary>
        /// Initializes a new instance of the <see cref="LocateInSolution"/> class.
        /// Adds our command handlers for menu (commands must exist in the command table file)
        /// </summary>
        /// <param name="package">Owner package, not null.</param>
        private LocateInSolution(Package package)
        {
            if (package == null)
            {
                throw new ArgumentNullException("package");
            }

            this.package = package;

            _applicationObject = this.ServiceProvider.GetService(typeof(DTE)) as DTE2;
            OleMenuCommandService commandService = this.ServiceProvider.GetService(typeof(IMenuCommandService)) as OleMenuCommandService;
            if (commandService != null)
            {
                var menuCommandID = new CommandID(CommandSet, CommandId);
                var menuItem = new MenuCommand(this.MenuItemCallback, menuCommandID);
                commandService.AddCommand(menuItem);
            }
        }

        /// <summary>
        /// Gets the instance of the command.
        /// </summary>
        public static LocateInSolution Instance
        {
            get;
            private set;
        }

        /// <summary>
        /// Gets the service provider from the owner package.
        /// </summary>
        private IServiceProvider ServiceProvider
        {
            get
            {
                return this.package;
            }
        }

        /// <summary>
        /// Initializes the singleton instance of the command.
        /// </summary>
        /// <param name="package">Owner package, not null.</param>
        public static void Initialize(Package package)
        {
            Instance = new LocateInSolution(package);
        }

        /// <summary>
        /// This function is the callback used to execute the command when the menu item is clicked.
        /// See the constructor to see how the menu item is associated with this function using
        /// OleMenuCommandService service and MenuCommand class.
        /// </summary>
        /// <param name="sender">Event sender.</param>
        /// <param name="e">Event args.</param>
        private void MenuItemCallback(object sender, EventArgs e)
        {
            FindCurrentActiveDocumentInSolutionExplorer();
        }

        private void FindCurrentActiveDocumentInSolutionExplorer()
        {
            ProjectItem projectItem = _applicationObject.ActiveDocument.ProjectItem;
            UIHierarchyItems solutionItems = _applicationObject.ToolWindows.SolutionExplorer.UIHierarchyItems;

            // check if we have a solution 
            if (solutionItems.Count != 1)
                return;

            // FindHierarchyItem expands nodes as well (it must do so, because children arent loaded until expanded) 
            UIHierarchyItem uiItem = FindHierarchyItem(solutionItems.Item(1).UIHierarchyItems, projectItem);

            if (uiItem != null)
            {
                _applicationObject.Windows.Item(EnvDTE.Constants.vsWindowKindSolutionExplorer).Activate();
                uiItem.Select(vsUISelectionType.vsUISelectionTypeSelect);
            }
        }

        private UIHierarchyItem FindHierarchyItem(UIHierarchyItems items, object item)
        {
            // 
            // Enumerating children recursive would work, but it may be slow on large solution. 
            // This tries to be smarter and faster 
            // 

            Stack stack = new Stack();
            CreateItemsStack(stack, item);

            UIHierarchyItem last = null;
            while (stack.Count != 0)
            {
                if (!items.Expanded)
                {
                    items.Expanded = true;
                }
                if (!items.Expanded)
                {
                    // bug: expand doesn't always work... 
                    UIHierarchyItem parent = ((UIHierarchyItem)items.Parent);
                    parent.Select(vsUISelectionType.vsUISelectionTypeSelect);
                    _applicationObject.ToolWindows.SolutionExplorer.DoDefaultAction();
                }

                object o = stack.Pop();

                last = null;
                foreach (UIHierarchyItem child in items)
                {
                    if (child.Object == o)
                    {
                        last = child;
                        items = child.UIHierarchyItems;
                        break;
                    }
                }
            }

            return last;
        }

        private void CreateItemsStack(Stack s, object item)
        {
            if (item is ProjectItem)
            {
                ProjectItem pi = (ProjectItem)item;
                s.Push(pi);
                CreateItemsStack(s, pi.Collection.Parent);
            }
            else if (item is Project)
            {
                Project p = (Project)item;
                s.Push(p);
                if (p.ParentProjectItem != null)
                {
                    // top nodes dont have solution as parent, but is null 
                    CreateItemsStack(s, p.ParentProjectItem);
                }
            }
            else if (item is Solution)
            {
                // do nothing
            }
            else
            {
                throw new ApplicationException("unknown item");
            }
        }
    }
}
